home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / X11 / unclutter-2.7 / unclutter.c < prev    next >
C/C++ Source or Header  |  1995-06-22  |  11KB  |  332 lines

  1. /*
  2.  * unclutter: remove idle cursor image from screen so that it doesnt
  3.  * obstruct the area you are looking at.
  4.  * doesn't do it if cursor is in root window or a button is down.
  5.  * Tries to cope with jitter if you have a mouse that twitches.
  6.  * Unfortunately, clients like emacs set different text cursor
  7.  * shapes depending on whether they have pointer focus or not.
  8.  * Try to kid them with a synthetic EnterNotify event.
  9.  * Whereas version 1 did a grab cursor, version 2 creates a small subwindow.
  10.  * This may work better with some window managers.
  11.  * Some servers return a Visibility event when the subwindow is mapped.
  12.  * Sometimes this is Unobscured, or even FullyObscured. Ignore these and
  13.  * rely on LeaveNotify events. (An InputOnly window is not supposed to get
  14.  * visibility events.)
  15.  * Mark M Martin. cetia sep 1992  mmm@cetia.fr
  16.  */
  17. #include <X11/Xos.h>
  18. #include <X11/Xlib.h>
  19. #include <X11/Xutil.h>
  20. #include <X11/Xproto.h>
  21. #include <stdio.h>
  22. #include "vroot.h"
  23.  
  24. char *progname;
  25. pexit(str)char *str;{
  26.     fprintf(stderr,"%s: %s\n",progname,str);
  27.     exit(1);
  28. }
  29. usage(){
  30.     pexit("usage:\n\
  31.     -display <display>\n\
  32.     -idle <seconds>        time between polls to detect idleness.\n\
  33.     -jitter <pixels>    pixels mouse can twitch without moving\n\
  34.     -grab            use grabpointer method not createwindow\n\
  35.     -reset            reset the timer whenever cursor becomes\n\
  36.                     visible even if it hasn't moved\n\
  37.      -root                   apply to cursor on root window too\n\
  38.     -onescreen        apply only to given screen of display\n\
  39.      -visible               ignore visibility events\n\
  40.      -noevents              dont send pseudo events\n\
  41.     -not names...        dont apply to windows whose wm-name begins.\n\
  42.                 (must be last argument)");
  43. }
  44.  
  45. #define ALMOSTEQUAL(a,b) (abs(a-b)<=jitter)
  46. #define ANYBUTTON (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
  47.  
  48. /* Since the small window we create is a child of the window the pointer is
  49.  * in, it can be destroyed by its adoptive parent.  Hence our destroywindow()
  50.  * can return an error, saying it no longer exists.  Similarly, the parent
  51.  * window can disappear while we are trying to create the child. Trap and
  52.  * ignore these errors.
  53.  */
  54. int (*defaulthandler)();
  55. int errorhandler(display,error)
  56. Display *display;
  57. XErrorEvent *error;
  58. {
  59.     if(error->error_code!=BadWindow)
  60.     (*defaulthandler)(display,error);
  61. }
  62.  
  63. char **names;    /* -> argv list of names to avoid */
  64.  
  65. /*
  66.  * return true if window has a wm_name and the start of it matches
  67.  * one of the given names to avoid
  68.  */
  69. nameinlist(display,window)
  70. Display *display;
  71. Window window;
  72. {
  73.     char **cpp;
  74.     char *name;
  75.  
  76.     if(names==0)return 0;
  77.     if(XFetchName (display, window, &name)){
  78.     for(cpp = names;*cpp!=0;cpp++){
  79.         if(strncmp(*cpp,name,strlen(*cpp))==0)
  80.         break;
  81.     }
  82.     XFree(name);
  83.     return(*cpp!=0);
  84.     }
  85.     return 0;
  86. }    
  87. /*
  88.  * create a small 1x1 curssor with all pixels masked out on the given screen.
  89.  */
  90. createnullcursor(display,root)
  91. Display *display;
  92. Window root;
  93. {
  94.     Pixmap cursormask;
  95.     XGCValues xgc;
  96.     GC gc;
  97.     XColor dummycolour;
  98.     Cursor cursor;
  99.  
  100.     cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/);
  101.     xgc.function = GXclear;
  102.     gc =  XCreateGC(display, cursormask, GCFunction, &xgc);
  103.     XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
  104.     dummycolour.pixel = 0;
  105.     dummycolour.red = 0;
  106.     dummycolour.flags = 04;
  107.     cursor = XCreatePixmapCursor(display, cursormask, cursormask,
  108.           &dummycolour,&dummycolour, 0,0);
  109.     XFreePixmap(display,cursormask);
  110.     XFreeGC(display,gc);
  111.     return cursor;
  112. }
  113.  
  114. main(argc,argv)char **argv;{
  115.     Display *display;
  116.     int screen,oldx = -99,oldy = -99,numscreens;
  117.     int doroot = 0, jitter = 0, idletime = 5, usegrabmethod = 0, waitagain = 0,
  118.     dovisible = 1, doevents = 1, onescreen = 0;
  119.     Cursor *cursor;
  120.     Window *realroot;
  121.     Window root;
  122.     char *displayname = 0;
  123.     
  124.     progname = *argv;
  125.     argc--;
  126.     while(argv++,argc-->0){
  127.     if(strcmp(*argv,"-idle")==0){
  128.         argc--,argv++;
  129.         if(argc<0)usage();
  130.         idletime = atoi(*argv);
  131.     }else if(strcmp(*argv,"-jitter")==0){
  132.         argc--,argv++;
  133.         if(argc<0)usage();
  134.         jitter = atoi(*argv);
  135.     }else if(strcmp(*argv,"-noevents")==0){
  136.         doevents = 0;
  137.     }else if(strcmp(*argv,"-root")==0){
  138.         doroot = 1;
  139.     }else if(strcmp(*argv,"-grab")==0){
  140.         usegrabmethod = 1;
  141.     }else if(strcmp(*argv,"-reset")==0){
  142.         waitagain = 1;
  143.     }else if(strcmp(*argv,"-onescreen")==0){
  144.         onescreen = 1;
  145.     }else if(strcmp(*argv,"-visible")==0){
  146.         dovisible = 0;
  147.     }else if(strcmp(*argv,"-not")==0){
  148.         /* take rest of srg list */
  149.         names = ++argv;
  150.         if(*names==0)names = 0;    /* no args follow */
  151.         argc = 0;
  152.     }else if(strcmp(*argv,"-display")==0 || strcmp(*argv,"-d")==0){
  153.         argc--,argv++;
  154.         if(argc<0)usage();
  155.         displayname = *argv;
  156.     }else usage();
  157.     }
  158.     display = XOpenDisplay(displayname);
  159.     if(display==0)pexit("could not open display");
  160.     numscreens = ScreenCount(display);
  161.     cursor = (Cursor*) malloc(numscreens*sizeof(Cursor));
  162.     realroot = (Window*) malloc(numscreens*sizeof(Window));
  163.  
  164.     /* each screen needs its own empty cursor.
  165.      * note each real root id so can find which screen we are on
  166.      */
  167.     for(screen = 0;screen<numscreens;screen++)
  168.     if(onescreen && screen!=DefaultScreen(display)){
  169.         realroot[screen] = -1;
  170.         cursor[screen] = -1;
  171.     }else{
  172.         realroot[screen] = XRootWindow(display,screen);
  173.         cursor[screen] = createnullcursor(display,realroot[screen]);
  174.     }
  175.     screen = DefaultScreen(display);
  176.     root = VirtualRootWindow(display,screen);
  177.  
  178.     if(!usegrabmethod)
  179.     defaulthandler = XSetErrorHandler(errorhandler);
  180.     /*
  181.      * create a small unmapped window on a screen just so xdm can use
  182.      * it as a handle on which to killclient() us.
  183.      */
  184.     XCreateWindow(display, realroot[screen], 0,0,1,1, 0, CopyFromParent,
  185.          InputOutput, CopyFromParent, 0, (XSetWindowAttributes*)0);
  186.  
  187.     while(1){
  188.     Window dummywin,windowin,newroot;
  189.     int rootx,rooty,winx,winy;
  190.     unsigned int modifs;
  191.     Window lastwindowavoided = None;
  192.     
  193.     /* wait for pointer to not move and no buttons down */
  194.     while(1){
  195.         if(!XQueryPointer(display, root, &newroot, &windowin,
  196.              &rootx, &rooty, &winx, &winy, &modifs)){
  197.         /* window manager with virtual root may have restarted
  198.          * or we have changed screens */
  199.         if(!onescreen){
  200.             for(screen = 0;screen<numscreens;screen++)
  201.             if(newroot==realroot[screen])break;
  202.             if(screen>=numscreens)
  203.             pexit("not on a known screen");
  204.         }
  205.         root = VirtualRootWindow(display,screen);
  206.         }else if((!doroot && windowin==None) || (modifs & ANYBUTTON) ||
  207.              !(ALMOSTEQUAL(rootx,oldx) && ALMOSTEQUAL(rooty,oldy))){
  208.         oldx = rootx, oldy = rooty;
  209.         }else if(windowin==None){
  210.         windowin = root;
  211.         break;
  212.         }else if(windowin!=lastwindowavoided){
  213.         /* descend tree of windows under cursor to bottommost */
  214.         Window childin;
  215.         int toavoid = xFalse;
  216.         lastwindowavoided = childin = windowin;
  217.         do{
  218.             windowin = childin;
  219.             if(nameinlist (display, windowin)){
  220.             toavoid = xTrue;
  221.             break;
  222.             }
  223.         }while(XQueryPointer(display, windowin, &dummywin,
  224.              &childin, &rootx, &rooty, &winx, &winy, &modifs)
  225.                && childin!=None);
  226.         if(!toavoid){
  227.             lastwindowavoided = None;
  228.             break;
  229.         }
  230.         }
  231.         sleep(idletime);
  232.     }
  233.     /* wait again next time */
  234.     if(waitagain)
  235.         oldx = -1-jitter;
  236.     if(usegrabmethod){
  237.         if(XGrabPointer(display, root, 0,
  238.             PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
  239.             GrabModeAsync, GrabModeAsync, None, cursor[screen],
  240.             CurrentTime)==GrabSuccess){
  241.         /* wait for a button event or large cursor motion */
  242.         XEvent event;
  243.         do{
  244.             XNextEvent(display,&event);
  245.         }while(event.type==MotionNotify &&
  246.                ALMOSTEQUAL(rootx,event.xmotion.x) &&
  247.                ALMOSTEQUAL(rooty,event.xmotion.y));
  248.         XUngrabPointer(display, CurrentTime);
  249.         }
  250.     }else{
  251.         XSetWindowAttributes attributes;
  252.         XEvent event;
  253.         Window cursorwindow;
  254.         
  255.         /* create small input-only window under cursor
  256.          * as a sub window of the window currently under the cursor
  257.          */
  258.         attributes.event_mask = LeaveWindowMask |
  259.             EnterWindowMask |
  260.             StructureNotifyMask |
  261.             FocusChangeMask;
  262.         if(dovisible)
  263.         attributes.event_mask |= VisibilityChangeMask;
  264.         attributes.override_redirect = True;
  265.         attributes.cursor = cursor[screen];
  266.         cursorwindow = XCreateWindow
  267.         (display, windowin,
  268.          winx-jitter, winy-jitter,
  269.          jitter*2+1, jitter*2+1, 0, CopyFromParent,
  270.          InputOnly, CopyFromParent, 
  271.          CWOverrideRedirect | CWEventMask | CWCursor,
  272.          &attributes);
  273.         /* discard old events for previously created windows */
  274.         XSync(display,True);
  275.         XMapWindow(display,cursorwindow);
  276.         /*
  277.          * Dont wait for expose/map cos override and inputonly(?).
  278.          * Check that created window captured the pointer by looking
  279.          * for inevitable EnterNotify event that must follow MapNotify.
  280.          * [Bug fix thanks to Charles Hannum <mycroft@ai.mit.edu>]
  281.          */
  282.         XSync(display,False);
  283.         if(!XCheckTypedWindowEvent(display, cursorwindow, EnterNotify,
  284.                       &event))
  285.         oldx = -1-jitter;    /* slow down retry */
  286.         else{
  287.         if(doevents){
  288.             /*
  289.              * send a pseudo EnterNotify event to the parent window
  290.              * to try to convince application that we didnt really leave it
  291.              */
  292.             event.xcrossing.type = EnterNotify;
  293.             event.xcrossing.display = display;
  294.             event.xcrossing.window = windowin;
  295.             event.xcrossing.root = root;
  296.             event.xcrossing.subwindow = None;
  297.             event.xcrossing.time = CurrentTime;
  298.             event.xcrossing.x = winx;
  299.             event.xcrossing.y = winy;
  300.             event.xcrossing.x_root = rootx;
  301.             event.xcrossing.y_root = rooty;
  302.             event.xcrossing.mode = NotifyNormal;
  303.             event.xcrossing.same_screen = True;
  304.             event.xcrossing.focus = True;
  305.             event.xcrossing.state = modifs;
  306.             (void)XSendEvent(display,windowin,
  307.                      True/*propagate*/,EnterWindowMask,&event);
  308.         }
  309.         /* wait till pointer leaves window */
  310.         do{
  311.             XNextEvent(display,&event);
  312.         }while(event.type!=LeaveNotify &&
  313.                event.type!=FocusOut &&
  314.                event.type!=UnmapNotify &&
  315.                event.type!=ConfigureNotify &&
  316.                event.type!=CirculateNotify &&
  317.                event.type!=ReparentNotify &&
  318.                event.type!=DestroyNotify &&
  319.                (event.type!=VisibilityNotify ||
  320.             event.xvisibility.state==VisibilityUnobscured)
  321.                );
  322.         /* check if a second unclutter is running cos they thrash */
  323.         if(event.type==LeaveNotify &&
  324.            event.xcrossing.window==cursorwindow &&
  325.            event.xcrossing.detail==NotifyInferior)
  326.             pexit("someone created a sub-window to my sub-window! giving up");
  327.         }
  328.         XDestroyWindow(display, cursorwindow);
  329.     }
  330.     }
  331. }
  332.